package optimizations.lexer;

import java.util.Comparator;

import java_cup.runtime.ComplexSymbolFactory.Location;

public class Position {

	@Override
	public String toString() {
		return "Position [columnEnd=" + columnEnd + ", columnStart="
				+ columnStart + ", lineEnd=" + lineEnd + ", lineStart="
				+ lineStart + ", unit=" + unit + "]";
	}

	public static final Position TOP = new Position(0, 0, 0, 0,
			BrainfuckLexer.DEFAULT_UNIT);

	private final int columnEnd;
	private final int columnStart;
	private final int lineEnd;
	private final int lineStart;
	private final String unit;

	/**
	 * Comparator for sorting {@link Position} objects by starting line and
	 * column.
	 */
	public static final Comparator<Position> START_COMPARATOR =
			new Comparator<Position>() {
		@Override
		public int compare(Position arg0, Position arg1) {
			int lineDiff = arg0.lineStart() - arg1.lineStart();
			if (lineDiff != 0) {
				return lineDiff;
			}
			return arg0.columnStart() - arg1.columnStart();
		}
	};

	/**
	 * Comparator for sorting {@link Position} objects by ending line and column.
	 */
	public static final Comparator<Position> END_COMPARATOR =
			new Comparator<Position>() {
		@Override
		public int compare(Position arg0, Position arg1) {
			int lineDiff = arg0.lineEnd() - arg1.lineEnd();
			if (lineDiff != 0) {
				return lineDiff;
			}
			return arg0.columnEnd() - arg1.columnEnd();
		}
	};

	public Position(int lineStart, int lineEnd, int columnStart,
			int columnEnd, String unit) {
		this.columnEnd = columnEnd;
		this.columnStart = columnStart;
		this.lineEnd = lineEnd;
		this.lineStart = lineStart;
		this.unit = unit;
	}

	public Position(Position position) {
		this.columnEnd = position.columnEnd();
		this.columnStart = position.columnStart();
		this.lineEnd = position.lineEnd();
		this.lineStart = position.lineStart();
		this.unit = position.unit();
	}

	public int columnEnd() {
		return columnEnd;
	}

	public int columnStart() {
		return columnStart;
	}

	public int lineEnd() {
		return lineEnd;
	}

	public int lineStart() {
		return lineStart;
	}

	public String unit() {
		return unit;
	}

	/**
	 * Static factory method for creating a starting Position from a compilation
	 * unit only
	 * 
	 * @param value String to create a Position for
	 * @return Position of start of file
	 */
	public static Position startPosition(String unit) {
		return new Position(1, 1, 1, 1, unit);
	}

	/**
	 * Static factory method for creating a Position from a text {@link String}
	 * and its starting point and unit.
	 * 
	 * @param value String to create a Position for
	 * @param lineStart value's starting line
	 * @param columnStart value's starting column
	 * @param unit value's compilation unit name
	 * @return Position of value
	 */
	public static Position of(String value, int lineStart, int columnStart,
			String unit) {
		return new Position(lineStart, endLineOf(value, lineStart),
				columnStart, endColumnOf(value, columnStart), unit);
	}

	/**
	 * Static factory method for creating a Position from start and end
	 * {@link Location}s and a compilation unit.
	 * 
	 * @param start starting Location
	 * @param end ending Location
	 * @return Position of value
	 */
	public static Position of(Location start, Location end, String unit) {
		return new Position(start.getLine(), end.getLine(), start.getColumn(),
				end.getColumn(), unit);
	}

	/**
	 * Static helper method for determining the line where a token ends.
	 * 
	 * @param s a String value
	 * @param lineStart the value's starting line
	 * @return the value's ending line
	 */
	static int endLineOf(String s, int lineStart) {
		// FIXME: extract this to a utility function
		int lineCount = 0;
		char[] sArray = s.toCharArray();
		for (int i = 0; i < sArray.length; i++) {
			switch (sArray[i]) {
			case '\r':
				// Consume \r\n as a single newline
				if (i < sArray.length && sArray[i + 1] == '\n') {
					i++;
				}
			case '\n':
				lineCount++;
				break;
			}
		}
		return lineStart + lineCount;
	}

	/**
	 * Static helper method for determining the column where a token ends.
	 * Behavior is undefined when the token ends with a newline character or
	 * whitespace.
	 * 
	 * @param s a String value
	 * @param columnStart the value's starting column
	 * @return the value's ending column
	 */
	static int endColumnOf(String s, int columnStart) {
		int lastIndex = Math.max(s.lastIndexOf('\n'), s.lastIndexOf('\r'));
		if (lastIndex < 0) {
			return columnStart + s.length() - 1;
		} else {
			return s.length() - lastIndex - 1;
		}
	}

}
